
package com.ys.chatserver.http.logic;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.eva.epc.common.file.FileHelper;
import com.eva.epc.common.util.CommonUtils;
import com.eva.epc.common.util.RestHashMap;
import com.eva.framework.Processor;
import com.eva.framework.dbpool.DBDepend;
import com.eva.framework.dbpool.DBShell;
import com.eva.framework.dto.SysActionConst;
import com.eva.framework.utils.EndsExceptionFactory;
import com.eva.framework.utils.LoggerFactory;
import com.ys.chatserver.BaseConf;
import com.ys.chatserver.MyControllerJSONP;
import com.ys.chatserver.cache.UsersInfoCacheProvider;
import com.ys.chatserver.http.logic.dto.JobDispatchConst;
import com.ys.chatserver.http.logic.dto.OfflineMsgDTO;
import com.ys.chatserver.http.logic.dto.PageData;
import com.ys.chatserver.http.logic.dto.RosterElementEntity;
import com.ys.chatserver.im.ChatServerEventListener;

import java.util.ArrayList;
import java.util.Objects;
import java.util.Vector;


/**
 * 基本Http业务接口处理器实现类。
 *
 * @author Tony ma, Jack Jiang
 */
public class LogicProcessor2 implements SysActionConst, Processor {
    public static DBShell db = new DBShell();

    /**
     * 处理器的核心实现方法.<br>
     * 可据job_dispatch_id找到对应的JobDispatcher，JobDispatcher又能据
     * action_id进行对应动作的处理，以完成本次操作请求.
     *
     * @param job_dispatch_id 作业调度ID
     * @param action_id       动作ID
     * @param _newData        newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param _oldData        oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param user            user 包含该请求用户完整个人信息封装对象（该对象来自于成功登陆后存放于session中的值）
     * @return 任意类型返回值
     */
    @Override
    public Object process(int job_dispatch_id, int action_id, Object _newData, Object _oldData, User user)
            throws Exception {
        Object ret = null;
        String newDataJSON = (String) _newData;

        switch (job_dispatch_id) {
            // 处理用户注册业务逻辑
            case JobDispatchConst.LOGIC_REGISTER:// ok
                ret = maintainRegisterJobDispatcher(action_id, newDataJSON, _oldData, user);
                break;

            // 好友管理逻辑
            case JobDispatchConst.LOGIC_ROSTER:// ok
                ret = maintainRosterMgrJobDispatcher(action_id, newDataJSON, _oldData, user);
                break;

            // 社交关系管理逻辑
            case JobDispatchConst.LOGIC_SNS:// ok
                ret = maintainSNSJobDispatcher(action_id, newDataJSON, _oldData, user);
                break;
            // 消息管理逻辑
            case JobDispatchConst.LOGIC_MESSAGES:
                ret = maintainMessagesJobDispatcher(action_id, newDataJSON, _oldData, user);
                break;
            // 删除好友
            case JobDispatchConst.LOGIC_DELETE$FRIEND:// ok
                ret = maintainDeleteFriendJobDispatcher(action_id, newDataJSON, _oldData, user);
                break;
            // 处理个人相册、个人介绍语音留言等个人详细信息资源的管理
            case JobDispatchConst.LOGIC_MGR$PROFILE:
                ret = maintainMgrProfileJobDispatcher(action_id, newDataJSON, _oldData, user);
                break;

            // 处理通用数据查询接口逻辑的管理
            // 说明：除了因历史原因已散落在其它区的查询代码，新增加的查询接口都应该放到本管理器里
            case JobDispatchConst.LOGIC_COMMON_QUERY_MGR:
                ret = maintainCommonQueryJobDispatcher(action_id, newDataJSON, _oldData, user);
                break;

            default:
                throw EndsExceptionFactory.INVALID_JOB_DISPATCHER_ID(job_dispatch_id);
        }
        return ret;
    }

    /**
     * 维护注册管理.
     *
     * @param actionId 维护类型，SysConstant中的操作类型常量
     * @param newData  客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param oldData  客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param user     操作者的用户名
     * @return 返回给客户端的对象
     */
    public String maintainRegisterJobDispatcher(int actionId
            , Object newData, Object oldData, User user) throws Exception {
        Vector<String> sqls = new Vector<>(2);
        Vector<Object[]> values = new Vector<>(2);

        switch (actionId) {

            // 【接口1008-1-8】修改个人信息，包括昵称、性别
            // 》当前正在被使用于：APP端
            case ACTION_APPEND2: {
                JSONObject nwObj = JSONObject.parseObject((String) newData);

                sqls.add("UPDATE g_users SET nickname=?,user_sex=? WHERE del_flag = 0 and user_uid=?");
                values.add(new Object[]{nwObj.getString("nickName")
                        , nwObj.getString("sex"), nwObj.getString("uid")});
                boolean res = db.update(sqls, values, true);

                //** 【高速缓存】即时更新用户缓存数据（因为更新了用户的昵称）
                UsersInfoCacheProvider.getInstance().reloadOneFromDB(nwObj.getString("uid"));

                return res ? "1" : "0";// 1 表示更新成功，否则失败
            }
            // 【接口1008-1-9】修改登陆密码
            // 》当前正在被使用于：APP端、Web端
            case ACTION_APPEND3: {
                JSONObject nwObj = JSONObject.parseObject((String) newData);

                // 返回值：1 表示更新成功，0 表示失败，2 表示原密码不正确
                String ret = "0";

                String sql = "SELECT user_psw FROM g_users WHERE del_flag = 0 and user_uid = '"
                        + nwObj.getString("uid")
                        + "'";
                String oldPsw = db.querySingleItem(sql);
                if (!CommonUtils.isStringEmpty(oldPsw)) {
                    if (oldPsw.equals(CommonUtils.encyptPassword(nwObj.getString("old_psw"), ""))) {
                        sqls.add("UPDATE g_users SET user_psw=? WHERE del_flag = 0 and user_uid=? AND user_psw=?");
                        values.add(new Object[]{
                                CommonUtils.encyptPassword(nwObj.getString("psw"), "")
                                , nwObj.getString("uid")
                                , CommonUtils.encyptPassword(nwObj.getString("old_psw"), "")
                        });
                        boolean res = db.update(sqls, values, true);
                        ret = res ? "1" : "0";
                    } else {
                        ret = "2";
                    }
                }
                return ret;
            }
            // 【接口1008-1-22】更新个性签名（what's up）
            // 》当前正在被使用于：APP端
            case ACTION_APPEND4: {
                JSONObject nwObj = JSONObject.parseObject((String) newData);
                sqls.add("UPDATE g_users SET what_s_up=? WHERE del_flag = 0 and user_uid=?");
                values.add(new Object[]{nwObj.getString("whats_up"), nwObj.getString("uid")});
                boolean res = db.update(sqls, values, true);
                return res ? "1" : "0";// 1 表示更新成功，否则失败
            }
            // 【接口1008-1-24】更新个人其它说明
            // 》当前正在被使用于：APP端
            case ACTION_APPEND6: {
                JSONObject nwObj = JSONObject.parseObject((String) newData);
                sqls.add("UPDATE g_users SET user_desc=? WHERE del_flag = 0 and user_uid=?");
                values.add(new Object[]{nwObj.getString("user_desc"), nwObj.getString("uid")});
                boolean res = db.update(sqls, values, true);
                return res ? "1" : "0";// 1 表示更新成功，否则失败
            }
            // 【接口1008-1-25】更新个人信息中的昵称、性别、个性签名、其它说明的综合接口
            // 》当前正在被使用于：Web端（当对接RainbowChat-Web产品时）
            // 》说明：本综合接口目前主要是用于Web版中的修改个人信息的功能。当然，并不限制它的使用场景，请按需使用。
            case ACTION_APPEND7: {
                JSONObject nwObj = JSONObject.parseObject((String) newData);

                sqls.add("UPDATE g_users SET nickname=?,user_sex=?,what_s_up=?,user_desc=? WHERE del_flag = 0 and user_uid=?");
                values.add(new Object[]{
                        nwObj.getString("nickName")
                        , nwObj.getString("sex")
                        , nwObj.getString("whats_up")
                        , nwObj.getString("user_desc")
                        , nwObj.getString("uid")});
                boolean res = db.update(sqls, values, true);

                //** 【高速缓存】即时更新用户缓存数据（因为更新了用户的昵称）
                UsersInfoCacheProvider.getInstance().reloadOneFromDB(nwObj.getString("uid"));

                return res ? "1" : "0";// 1 表示更新成功，否则失败
            }

            default:
                throw EndsExceptionFactory.INVALID_ACTION_ID(actionId);
        }
    }

    /**
     * 维护好友管理.
     *
     * @param action_id 维护类型，SysConstant中的操作类型常量
     * @param newData   客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param oldData   客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param user      操作者的用户名
     * @return 返回给客户端的对象
     */
    public String maintainRosterMgrJobDispatcher(int action_id, Object newData, Object oldData, User user) throws Exception {
        Vector<String> sqls = new Vector<>(2);
        Vector<Object[]> values = new Vector<>(2);

        switch (action_id) {
            // 【接口1008-2-7】获取好友列表过程
            case ACTION_APPEND1://ok
            {
                // 客户端传过来的是UID（不会是mail，因为客户端已经处理过了）
                String uid = (String) newData;
                ArrayList<RosterElementEntity> rosters = new ArrayList<RosterElementEntity>();

                // 该用户的好友列表
                Vector<Vector<?>> roster = db.queryData(
                        "select a.friend_user_uid,b.user_mail,b.nickname" +
                                "		,b.user_sex,b.user_avatar_file_name,b.what_s_up " +
                                "		,a.remark,a.mobile_num,a.more_desc,a.pic_file_name  " +
                                "	from chat_my_firends a " +
                                "	left join g_users b on a.friend_user_uid = b.user_uid " +
                                "	where b.del_flag = 0 and a.user_uid=?", new Object[]{uid});
                // 遍历好友列表，设置好友数据
                for (Vector<?> friendRow : roster) {
                    int i = 0;
                    // 好友uid
                    String friend_user_uid = (String) friendRow.get(i++);
                    String friend_user_mail = (String) friendRow.get(i++);
                    String friend_nickname = (String) friendRow.get(i++);
                    String friend_user_sex = (String) friendRow.get(i++);
                    String user_avatar_file_name = (String) friendRow.get(i++);
                    String what_s_up = (String) friendRow.get(i++);

                    String remark = (String) friendRow.get(i++);
                    String mobile_num = (String) friendRow.get(i++);
                    String more_desc = (String) friendRow.get(i++);
                    String pic_file_name = (String) friendRow.get(i++);

                    RosterElementEntity friend = new RosterElementEntity();

                    ChatServerEventListener.getInstance().getLiverUsersMgr().printLiveUsers();
                    // 遍历在线好友列表，看看该好友是否在线（在线用户列表中的key可能是user_uid也可能是user_mail，
                    // 因为客户端允许用户使用uid和mail二种方式登陆）
                    if (ChatServerEventListener.getInstance().getLiverUsersMgr().isOnline(friend_user_uid)) {
                        // 设置在线状态为“在线”
                        friend.setLiveStatus(1);
                    }
                    friend.setUser_uid(friend_user_uid);
                    friend.setUser_mail(friend_user_mail);
                    friend.setNickname(friend_nickname);
                    friend.setUser_sex(Integer.valueOf(friend_user_sex));
                    friend.setUserAvatarFileName(user_avatar_file_name);
                    friend.setWhatsUp(what_s_up);

                    friend.setFriendRemark(remark);
                    friend.setFriendMobileNum(mobile_num);
                    friend.setFriendMoreDesc(more_desc);
                    friend.setFriendPicFileName(pic_file_name);

                    rosters.add(friend);
                }

                return JSON.toJSONString(rosters);
            }
            // 【接口1008-2-8】更新好友信息中的备注、描述等的接口
            // 》当前正在被使用于：Andriod端、iOS端、Web端
            case ACTION_APPEND2: {
                JSONObject nwObj = JSONObject.parseObject((String) newData);
                sqls.add("UPDATE chat_my_firends SET remark=?,mobile_num=?,more_desc=? WHERE user_uid=? and friend_user_uid=?");
                values.add(new Object[]{nwObj.getString("remark"), nwObj.getString("mobile_num")
                        , nwObj.getString("more_desc"), nwObj.getString("user_uid"), nwObj.getString("friend_user_uid")});
                boolean res = db.update(sqls, values, true);
                return res ? "1" : "0";// 1 表示更新成功，否则失败
            }
            default:
                throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
        }
    }

    /**
     * 维护好友关系管理.
     *
     * @param action_id 维护类型，SysConstant中的操作类型常量
     * @param newData   客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param _oldData  客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param user      操作者的用户名
     * @return 返回给客户端的对象
     */
    public String maintainSNSJobDispatcher(int action_id
            , Object newData, Object _oldData, User user) throws Exception {
        Object[] objs = (Object[]) _oldData;
        Object oldData = objs[1];

        Vector<String> sqls = new Vector<>();
        Vector<Object[]> values = new Vector<>();

        switch (action_id) {
            // 【接口1008-3-8】查询好友/陌生人的基本信息
            case ACTION_APPEND2:// ok
            {
                JSONObject nwObj = JSONObject.parseObject((String) newData);

                // true表示用用户或女孩友的mail地址，否则表示用用户或好友的uid
                boolean useMail = "1".equals(nwObj.getString("use_mail"));
                // 用户或的mail地址
                String mailOfFriend = nwObj.getString("friend_mail");
                // 用户或好友的uid
                String uidOfFriend = nwObj.getString("friend_uid");

                // 查询发起人的uid，这个uid指的是客户端提起这个查询时的当前登陆者uid，
                // 指明此uid后本sql将同时提供被查询作为好友的额外信息。本参数可为null（表示不需要查询好友的额外信息）
                String myUid = nwObj.getString("my_uid");

                return JSON.toJSONString(LogicUtils.queryUserInfo(useMail, mailOfFriend, uidOfFriend, myUid));
            }
            default:
                throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
        }
    }

    /**
     * 维护消息管理.
     *
     * @param action_id 维护类型，SysConstant中的操作类型常量
     * @param newData   客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param oldData   客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param user      操作者的用户名
     * @return 返回给客户端的对象
     */
    public String maintainMessagesJobDispatcher(int action_id
            , Object newData, Object oldData, User user) throws Exception {
        switch (action_id) {
            // 【接口1008-4-7】获取离线状态下好友发过来的加好友请求列表
            // FIXME: 性能优化点：以下查询其实可以一条SQL搞定，但目前为了简化字段解析代码而如此使用，以后再考虑进行查询优化（合并查询）
            case ACTION_APPEND1:// ok
            {
                ArrayList<RosterElementEntity> allRequestFriendsInfo = new ArrayList<RosterElementEntity>();

                String localUid = (String) newData;
                String sb = "select a.user_uid,a.be_timestamp,a.be_desc from missu_friendreq a " +
                        "join g_users b on a.be_user_uid=b.user_uid " +
                        "where b.user_uid='" + localUid + "' order by a.be_time desc";

                Vector<Vector<?>> vec = db.queryData(sb);
                if (!vec.isEmpty()) {
                    for (Vector<?> row : vec) {
                        // 解析行数据并构建对象
                        RosterElementEntity ree = constructOfflineAddFriendReqObj(row);
                        if (ree != null)
                            allRequestFriendsInfo.add(ree);
                    }
                }

                return JSON.toJSONString(allRequestFriendsInfo);
            }
            // 【接口1008-4-8】获取离线聊天消息
            case ACTION_APPEND2: {
                ArrayList<OfflineMsgDTO> allRequestFriendsInfo = new ArrayList<OfflineMsgDTO>();

                JSONObject nwObj = JSONObject.parseObject((String) newData);
                // 离线消息所有者的uid
                String user_uid = nwObj.getString("user_uid");
                // 离线消息由谁发送的uid（此参数为null则表示读取离线消息所有者的所有
                // 离线消息，否则表示只取指定的发送人发的离线消息）
                String from_user_uid = nwObj.getString("from_user_uid");
                // 离线消息中是否带上该消息在实时发送时的指纹码（目前仅在ios端、Andriod端需要
                // ，其它端暂未用到，请忽略本参数或传“0”即可）
                String includeFingerPring = nwObj.getString("include_fp");
                boolean isIncludeFingerPring = "1".equals(includeFingerPring);

                StringBuilder sb = new StringBuilder()
                        .append("select ")
                        .append("	a.user_uid")
                        .append("   ,b.nickname")
//				.append("	,b.user_mail" )
                        .append("	,a.friend_user_uid")
//				.append("	,c.user_mail" )
                        .append("	,a.history_type")
                        .append("	,a.history_content")
//				.append("	,a.history_content2" )
                        .append("	,a.history_time")
                        .append("	,a.chat_type")
                        .append("	,a.group_id")  // 只对群聊消息有意义（且只在群聊消息时不为null）
                        .append("	,d.g_name")    // 只对群聊消息有意义
                        .append(isIncludeFingerPring ? ",a.history_content2 as fp" : ",null as fp")
                        .append("	,a.parent_fp") // 只对群聊消息有意义
                        .append(" from chat_offline_history a ")
                        .append(" left join g_users b on a.user_uid = b.user_uid ")
//				.append(" left join g_users c on a.friend_user_uid = c.user_uid " +
                        .append(" left join chat_group_base d on a.group_id = d.g_id ")
                        .append(" where a.friend_user_uid = '" + user_uid + "'")
                        // 为null则表示读取离线消息所有者的所有离线消息，否则表示只取指定的发送人发的离线消息
                        .append((from_user_uid == null ? "" : " and a.user_uid='" + from_user_uid + "'"))
                        .append(" order by a.history_time");


                String sql = sb.toString();

                Vector<Vector<?>> vec = db.queryData(sql);
                if (!vec.isEmpty()) {
                    for (Vector<?> row : vec) {
                        OfflineMsgDTO tempChatMsg = new OfflineMsgDTO();
                        int i = 0;
                        tempChatMsg.setUser_uid((String) row.get(i++));
                        tempChatMsg.setNickName((String) row.get(i++));
//						tempChatMsg.setUser_mail((String) row.get(i++));
                        tempChatMsg.setFriend_user_uid((String) row.get(i++));
//						tempChatMsg.setFriendUserMail((String) row.get(i++));
                        tempChatMsg.setMsg_type(Integer.parseInt((String) row.get(i++)));
                        tempChatMsg.setMsg_content((String) row.get(i++));
//						tempChatMsg.setMsg_content2((String) row.get(i++));
                        tempChatMsg.setHistory_time2((String) row.get(i++));
                        tempChatMsg.setChat_type((String) row.get(i++));
                        tempChatMsg.setGroup_id((String) row.get(i++));
                        tempChatMsg.setGroup_name((String) row.get(i++));

                        tempChatMsg.setMsg_content2((String) row.get(i++));
                        tempChatMsg.setParent_fp((String) row.get(i++));

                        allRequestFriendsInfo.add(tempChatMsg);
                    }
                }

                Vector<String> sqls = new Vector<>();
                Vector<Object[]> values = new Vector<>();
                // 获取完离线消息后，就需要把水从离线列表中删除了哦
                sqls.add("delete from chat_offline_history where friend_user_uid = ?"
                        // 为null则表示读取离线消息所有者的所有离线消息（不区分好友），
                        // 否则表示只取指定的发送人发的离线消息
                        + (from_user_uid == null ? "" : " and user_uid='" + from_user_uid + "'"));
                values.add(new Object[]{user_uid});

                db.update(sqls, values, false);

                return JSON.toJSONString(allRequestFriendsInfo);
            }
            // 【接口1008-4-9】获取未处理的加好友请求总数
            // ·版本信息：since v4.5
            // ·用途说明：用于从服务端读取未处理的加好友请求总数量；
            // ·适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端。本接口后绪也将
            //           被用于替换现版Android和iOS里读取未处理好友总数的实现代码，当前暂未使用；
            case ACTION_APPEND3: {
                String localUid = (String) newData;

                //** 未处理的加好友请求总数量
                StringBuffer sb = new StringBuffer();
                sb.append("select count(a.user_uid) as cnt from missu_friendreq a ")
                        .append("join g_users b on a.be_user_uid=b.user_uid ")
                        .append("where b.del_flag = 0 and b.user_uid=?");
                // 提示：请坚持使用预编译SQL语句，不够精简，但JDBC会自动处理SQL注入风险，保证安全性！
                Vector<Vector<?>> vecs = db.queryData(sb.toString(), new Object[]{localUid});
                // 解析查询结果
                String cnt = (String) (vecs.get(0).get(0));//db.querySingleItem(sb.toString());
                int cntInt = CommonUtils.getIntValue(cnt, 0);

                //** 取出最后一个加好友的请求数据（方便客户端的ui上显示）
                boolean isSQLServer = DBShell.isSQLServer();
                boolean isMySQL = DBShell.isMySQL();
                StringBuffer sb2 = new StringBuffer();
                sb2.append("select " + (isSQLServer ? "top 1" : "") + " a.user_uid,a.be_timestamp,a.be_desc from missu_friendreq a ")
                        .append("join g_users b on a.be_user_uid=b.user_uid ")
                        .append("where b.user_uid=? order by a.be_time desc " + (isMySQL ? "limit 0,4" : ""));
                Vector<Vector<?>> vecs2 = db.queryData(sb2.toString(), new Object[]{localUid});
                // 解析行数据并构建对象
                RosterElementEntity offlineAddFriendReqObj = null;
                if (!vecs2.isEmpty())
                    offlineAddFriendReqObj = constructOfflineAddFriendReqObj(vecs2.get(0));

                //** 返回给客户端的数据
                RestHashMap<String, Object> m = RestHashMap.n();
                // 未处理的请求总数
                m.p("req_count", String.valueOf(cntInt));
                // 最近一个加好友请求的数据封装对象
                if (offlineAddFriendReqObj != null)
                    m.putField("latest_req_obj", "" + JSON.toJSONString(offlineAddFriendReqObj));

                return JSON.toJSONString(m);
            }

            // 【接口1008-4-22】:删除个人全部聊天消息记录（已用于：Web端，目前其它端暂时用不上本接口）  - @since 4.5
            //  支持：删除一对一好友或陌生人聊天记录（即删除“我”与指定uid用户的记录）、群聊记录
            // ·用途说明：用于删除服务端存储的与某人或某群的全部聊天记录；
            // ·适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端（特别注意：因Android
            //           和iOS这类富客户，聊天记录都是存本地的，所以严格来说移动端不需要也没必要使用
            //           接口，本接口完全是为了对接Web产品时为其准备的）；
            case ACTION_APPEND4: {
                // 客户端传过来的数据
                JSONObject nwObj = JSONObject.parseObject((String) newData);
                //在池台的正中像当初的怀中隔太多春秋会不能相拥 还没到开满花却看见天边一点点变红还以为
                //无影踪记忆力又翻涌人长大后太难学从容总有事忙怎么像化蝶那么勇
                String gid = nwObj.getString("gid");   // 删除群聊记录必填参数：要删除聊天记录的群id

                String luid = nwObj.getString("luid"); // 删除单聊和群聊记录必填参数：local uid（即“我”的uid）
                String ruid = nwObj.getString("ruid"); // 删除单聊记录选填参数：remote uid（即“对方”的uid）

                // true表示当前要删除的是群聊消息记录，否则表示要删除的一对一聊天记录
                boolean isDeleteGroupChatting = (!CommonUtils.isStringEmpty(gid, true));

                Vector<String> sqls = new Vector<>();
                Vector<Object[]> values = new Vector<>();

                String ret = "0"; // 返回值：“0”表示失败、“1”表示成功

                try {
                    // 要“删除”的是群聊聊天记录
                    // 说明：群聊消息不是真的从物理上删除，只是逻辑上实现“删除”而已
                    if (isDeleteGroupChatting) {
                        // 更新此人在群内的消息起始时间即可实现消息的逻辑“删除”
                        sqls.add("update chat_group_node set msg_time_start=" + DBDepend.getDefaultDatetimeFunc() + " where g_id=? and user_uid=?");
                        values.add(new Object[]{gid, luid});

                        // 提交数据库执行
                        db.update(sqls, values, true);
                    }
                    // 要“删除”的是单聊聊天记录
                    else {
                        //********************* 先尝试存档聊天记录和删除未拉取的离线消息 *********//
                        LogicProcessor2.prepareDeleteSingleChattingMsg(sqls, values, luid, ruid);

                        // 提交数据库执行
                        db.update(sqls, values, false);
                    }

                    ret = "1"; // 成功
                } catch (Exception e) {
                    LoggerFactory.getLog().warn("发生在删除与某人或某群的全部聊天记录时：", e);
                }

                return ret;
            }
            // 【接口1008-4-23】删除单条聊天消息记录（已用于：Web端，目前其它端暂时用不上本接口）  - @since 7.3
            // ·特别说明：本接口是真正的物理删除，不是转储哦！
            // ·用途说明：用于删除服务端存储的与某人或某群的单条聊天记录；
            // ·适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端（特别注意：因Android
            //           和iOS这类富客户，聊天记录都是存本地的，所以严格来说移动端不需要也没必要使用
            //           接口，本接口完全是为了对接Web产品时为其准备的）；
            case ACTION_APPEND5: {
                LoggerFactory.getLog().debug("==【【【【【【【正在删除单条聊天消息。。。】】】】】】】==");
                JSONObject nwObj = JSONObject.parseObject((String) newData);
                String fpForMessage = nwObj.getString("fp_for_message");

                Vector<String> sqls = new Vector<>();
                Vector<Object[]> values = new Vector<>();
                sqls.add("delete from chat_msgs_collect where fingerprint=?");
                values.add(new Object[]{fpForMessage});
                boolean res = db.update(sqls, values, false);
                return res ? "1" : "0";// 1 表示更新成功，否则失败
            }
            //【接口1008-4-24】获取7天历史消息记录
            case ACTION_APPEND6: {
                ArrayList<OfflineMsgDTO> allRequestFriendsInfo = new ArrayList<OfflineMsgDTO>();
                JSONObject nwObj = JSONObject.parseObject((String) newData);
                String user_uid = nwObj.getString("user_uid");
                //String sqlGid = "select g_id from chat_group_base where g_status=1 and g_id in (select base_id from chat_group_node where user_uid='" + user_uid + "')";
                final String sqlGid = "select CONCAT(base_id,\"_\",node_pid)as g_id FROM chat_group_node WHERE user_uid = '" + user_uid + "'";
                String sql = "select a.src_uid,b.nickname,a.dest_uid,a.msg_type," +
                        "a.msg_content,a.msg_time2,a.chat_type," +
                        "a.dest_uid as g_id," +
                        "(select g_name from chat_group_base where g_status = 1 and g_id = SUBSTRING_INDEX(dest_uid,\"_\",1)) as g_name," +
                        "a.fingerprint,a.parent_fp from chat_msgs_collect a " +
                        "LEFT JOIN g_users b on a.src_uid=b.user_uid " +
                        "WHERE b.del_flag = 0 and msg_type!=90 and msg_time>DATE_SUB(NOW(),INTERVAL 7 DAY) and " +
                        "(src_uid = '" + user_uid + "' or dest_uid = '" + user_uid + "' or " +
                        "src_uid in (" + sqlGid + ") or dest_uid in (" + sqlGid + ")) ORDER BY msg_time desc limit 0,200";
                Vector<Vector<?>> vec = db.queryData(sql);
                if (!vec.isEmpty()) {
                    for (Vector<?> row : vec) {
                        OfflineMsgDTO tempChatMsg = new OfflineMsgDTO();
                        int i = 0;
                        tempChatMsg.setUser_uid((String) row.get(i++));
                        tempChatMsg.setNickName((String) row.get(i++));
                        tempChatMsg.setFriend_user_uid((String) row.get(i++));
                        tempChatMsg.setMsg_type(Integer.parseInt((String) row.get(i++)));
                        tempChatMsg.setMsg_content((String) row.get(i++));
                        tempChatMsg.setHistory_time2((String) row.get(i++));
                        tempChatMsg.setChat_type((String) row.get(i++));
                        tempChatMsg.setGroup_id((String) row.get(i++));
                        tempChatMsg.setGroup_name((String) row.get(i++));
                        tempChatMsg.setMsg_content2((String) row.get(i++));
                        tempChatMsg.setParent_fp((String) row.get(i++));

                        if (Integer.parseInt(tempChatMsg.getChat_type()) == 2) {
                            tempChatMsg.setFriend_user_uid("0");
                        }
                        allRequestFriendsInfo.add(tempChatMsg);
                    }
                }
                return JSONObject.toJSONString(allRequestFriendsInfo);

            }
            //【接口1008-4-24】获取7天历史消息记录(分页)
            case ACTION_APPEND14: {
                ArrayList<OfflineMsgDTO> allRequestFriendsInfo = new ArrayList<OfflineMsgDTO>();
                JSONObject nwObj = JSONObject.parseObject((String) newData);
                String user_uid = nwObj.getString("user_uid");
                Integer pageNumber = nwObj.getIntValue("page_number");
                Integer pageSize = nwObj.getInteger("page_size");

                //final String sqlGid = "select g_id from chat_group_base where g_status=1 and g_id in (select base_id from chat_group_node where user_uid='" + user_uid + "')";

                final String sqlGid = "select CONCAT(base_id,\"_\",node_pid)as g_id FROM chat_group_node WHERE user_uid = '" + user_uid + "'";
                final String sql = "select a.src_uid,b.nickname,a.dest_uid,a.msg_type," +
                        "a.msg_content,a.msg_time2,a.chat_type," +
                        "a.dest_uid as g_id," +
                        "(select g_name from chat_group_base where g_status = 1 and g_id = SUBSTRING_INDEX(dest_uid,\"_\",1)) as g_name," +
                        "a.fingerprint,a.parent_fp from chat_msgs_collect a " +
                        "LEFT JOIN g_users b on a.src_uid=b.user_uid " +
                        "WHERE msg_type!=90 and msg_time>DATE_SUB(NOW(),INTERVAL 7 DAY) and " +
                        "(src_uid = '" + user_uid + "' or dest_uid = '" + user_uid + "' or " +
                        "src_uid in (" + sqlGid + ") or dest_uid in (" + sqlGid + ")) ORDER BY msg_time ASC"
                        + limitSql(pageNumber, pageSize);

                final String totalSql = "select count(1) from chat_msgs_collect a " +
                        "LEFT JOIN g_users b on a.src_uid=b.user_uid " +
                        "LEFT JOIN chat_group_base c on a.dest_uid=c.g_id " +
                        "WHERE msg_type!=90 and msg_time>DATE_SUB(NOW(),INTERVAL 7 DAY) and " +
                        "(src_uid = '" + user_uid + "' or dest_uid = '" + user_uid + "' or " +
                        "src_uid in (" + sqlGid + ") or dest_uid in (" + sqlGid + ")) ;";
                Vector<Vector<?>> vec = db.queryData(sql);
                if (!vec.isEmpty()) {
                    for (Vector<?> row : vec) {
                        OfflineMsgDTO tempChatMsg = new OfflineMsgDTO();
                        int i = 0;
                        tempChatMsg.setUser_uid((String) row.get(i++));
                        tempChatMsg.setNickName((String) row.get(i++));
                        tempChatMsg.setFriend_user_uid((String) row.get(i++));
                        tempChatMsg.setMsg_type(Integer.parseInt((String) row.get(i++)));
                        tempChatMsg.setMsg_content((String) row.get(i++));
                        tempChatMsg.setHistory_time2((String) row.get(i++));
                        tempChatMsg.setChat_type((String) row.get(i++));
                        tempChatMsg.setGroup_id((String) row.get(i++));
                        tempChatMsg.setGroup_name((String) row.get(i++));
                        tempChatMsg.setMsg_content2((String) row.get(i++));
                        tempChatMsg.setParent_fp((String) row.get(i++));

                        if (Integer.parseInt(tempChatMsg.getChat_type()) == 2) {
                            tempChatMsg.setFriend_user_uid("0");
                        }
                        allRequestFriendsInfo.add(tempChatMsg);
                    }
                }
                long total = 0L;
                Vector<Vector<?>> count = db.queryData(totalSql);
                if (!count.isEmpty()) {
                    total = Long.parseLong((String) count.get(0).get(0));
                }
                PageData<OfflineMsgDTO> pageData = new PageData<>();
                pageData.setRecords(allRequestFriendsInfo);
                pageData.setTotal(total);
                pageData.setPageNumber(pageNumber);
                pageData.setPageSize(vec.size());
                return JSONObject.toJSONString(pageData);
            }


            default:
                throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
        }
    }

    private String limitSql(Integer pageNumber, Integer pageSize) {
        if (!Objects.isNull(pageNumber) && pageNumber > 0) {
            //默认分页15条
            pageSize = Objects.isNull(pageSize) ? 15 : pageSize;
            int pageNum = (pageNumber - 1) * pageSize;
            return " limit " + pageNum + "," + pageSize + ";";
        }
        return ";";
    }

    /**
     * 维护删除好友.
     *
     * @param action_id   维护类型，SysConstant中的操作类型常量
     * @param newDataJSON 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param oldData     客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param user        操作者的用户名
     * @return 返回给客户端的对象
     */
    public String maintainDeleteFriendJobDispatcher(int action_id
            , String newDataJSON, Object oldData, User user) throws Exception {
        switch (action_id) {
            // 【接口1008-5-7】删除好友
            case ACTION_APPEND1:// ok
            {
                // 客户端传过来的数据
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);

                String myUid = nwObj.getString("local_uid");
                String friendUid = nwObj.getString("friend_uid");

                Vector<String> sqls = new Vector<>();
                Vector<Object[]> values = new Vector<>();


                //********************* 先尝试存档聊天记录和删除未拉取的离线消息 *********//
                LogicProcessor2.prepareDeleteSingleChattingMsg(sqls, values, myUid, friendUid);


                //********************* 最后尝试删除好友关系记录 ************************//
                // 先把好友从我的好友列表中删除
                sqls.add("delete from chat_my_firends where user_uid=? and friend_user_uid=?");
                values.add(new Object[]{myUid, friendUid});
                // 再把我从好友的列表中删除
                sqls.add("delete from chat_my_firends where user_uid=? and friend_user_uid=?");
                values.add(new Object[]{friendUid, myUid});


                // 删除好友时，最佳逻辑是提前查询一下db看看是不是已被对主提前把“我”这个好友删除了，
                // 但为了性能——节省一次db查询，此处直接让它执行db删除插件，如果发生“影响行数为0”
                // 时即表示“我”已被对方提前删除。try catch的目的就是让此db执行效果静默执行，本来
                // 也不影响什么，防止将无意义的Exception发回给用户，影响体验
                try {
                    db.update(sqls, values, true);// Bug FIX 20170406：真的发生了只删除了一方好友信息。所以现改为必须事务，二条记录必须保证要么全在要么全不在
                } catch (Exception e) {
                    LoggerFactory.getLog().debug("[HTTP]在为用户" + myUid + "删除好友" + friendUid
                            + "时进入了非正常路径（\"" + e.getMessage() + "\"），原因可能是已提前被对方"
                            + "从好友列表中删除。（本行log出现不影响正常逻辑，请忽略之）");
                }

                return null;
            }

            default:
                throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
        }
    }

    /**
     * 维护个人相册、个人介绍语音留言等个人详细信息资源的管理.
     *
     * @param action_id   维护类型，SysConstant中的操作类型常量
     * @param newDataJSON 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param oldData     客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param user        操作者的用户名
     * @return 返回给客户端的对象
     */
    public Object maintainMgrProfileJobDispatcher(int action_id
            , String newDataJSON, Object oldData, User user) throws Exception {
        switch (action_id) {
            // 【接口1008-10-7】删除个人相册、个人介绍语音留言等个人详细信息资源，@since KChat_2.5
            case ACTION_APPEND1: {
                // 客户端传过来的数据
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);

                String resource_id = nwObj.getString("resourceId");
                String fileName = nwObj.getString("fileName");
                String resType = nwObj.getString("resType");

                if (CommonUtils.isStringEmpty(resource_id, true)) {
                    throw new IllegalArgumentException("[HTTP]无效的参数, resource_id=null!");
                }

                String dir = null;
                // 要删除的是相册
                if ("0".equals(resType))
                    dir = BaseConf.getInstance().getDIR_USER_PHOTO_UPLOAD();
                    // 要删除的是个人介绍语音文件
                else if ("1".equals(resType))
                    dir = BaseConf.getInstance().getDIR_USER_PVOICE_UPLOAD();
                else
                    throw new IllegalArgumentException("[HTTP]无效的参数, resType=" + resType);

                boolean fileDeleteSucess = false;
                // 先删除相片（原图）、语音文件
                fileDeleteSucess = FileHelper.deleteFile(dir + fileName);
                // 如果要删除的是相片则还要删除缩略图
                if ("0".equals(resType))
                    // 再删除相片的缩略图
                    FileHelper.deleteFile(dir + "th_" + fileName);

                // 再去删除数据库记录
                if (fileDeleteSucess) {
                    LoggerFactory.getLog().debug("[HTTP]成功删除了用户的个人资料资源文件：" + (dir + fileName));
                    //LogicProcessor2.db.update("delete from missu_users_binary where resource_id=?", new Object[]{resource_id}, false);
                }
                return null;
            }
            // 【接口1008-10-8】查询个人相册、个人介绍语音留言的数量（目前用于客户端个
            // 人信息查看界面中显示照片和语音预览数量时使用），@since 4.0
/*			case ACTION_APPEND2:
			{
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String uid = nwObj.getString("user_uid");// 要查询用户的uid
				
				Vector<Vector> vecs = db.queryData("select count(*), res_type from missu_users_binary where user_uid=" +
							uid+" and res_type in(0,1) group by res_type");
				return JSON.toJSONString(vecs);
			}*/
            // 【接口1008-10-9】查询个人相册、个人介绍语音留言的完整数据列表（目前用于客户端个
            // 人信息查看界面中显示照片和语音完整列表时使用），@since 4.0

/*			case ACTION_APPEND3:
			{
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String uid = nwObj.getString("user_uid");    // 要查询用户的uid
				String resType = nwObj.getString("res_type");// 要查询资源类型：0 表示相片、1 表示个人介绍语音

				Vector<Vector> vecs = db.queryData("select resource_id" +
						",res_file_name,res_human_size,res_size,view_count,create_time" +
						" from missu_users_binary where user_uid='"
						+uid+"' and res_type="+resType+" order by create_time desc");
				return JSON.toJSONString(vecs);
			}*/

            // 【接口1008-10-22】查询个人相册预览图片数据列表（目前用于客户端个
            // 人信息查看界面中显示照片预览列表时使用），@since 4.0
/*			case ACTION_APPEND4:
			{
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String uid = nwObj.getString("user_uid");    // 要查询用户的uid

				// 根据db类型的不同，决定选取前4行结果的sql该怎么写
				boolean isSQLServer = DBShell.isSQLServer();
				boolean isMySQL = DBShell.isMySQL();
				Vector<Vector> vecs = db.queryData("select "+(isSQLServer?"top 4":"")+" res_file_name" +
							" from missu_users_binary" +
							" where user_uid="+uid+" and res_type=0" +
							" order by create_time desc "+(isMySQL?"limit 0,4":""));
				return JSON.toJSONString(vecs);
			}*/
            default:
                throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
        }
    }

    /**
     * 维护通用数据查询接口的管理.
     *
     * @param action_id   维护类型，SysConstant中的操作类型常量
     * @param newDataJSON 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
     * @param _oldData    客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
     * @param user        操作者的用户名
     * @return 返回给客户端的对象
     */
    public String maintainCommonQueryJobDispatcher(int action_id
            , String newDataJSON, Object _oldData, User user) throws Exception {
        Vector<String> sqls = new Vector<>();
        Vector<Object[]> values = new Vector<>();

        switch (action_id) {
            // 【接口1008-26-7】: 具体说明，详见“RainbowChat4.0-HttpRest接口手册.pdf”！
            // 用途说明：用于查询（构造）客户端界面上首页“消息”列表；
            // 数据说明：本列表的数据，取自被查询用户与所有聊天对象的最近一次聊天记录（消息内容、消息类型、消息时间等）；
            // 适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端（虽然移动端等也可以用，但没有必要，原因为
            //          Android或iOS端有本地缓存数据，直接本地构建这个数据列表即可，无需连服务端查询）；
            case ACTION_APPEND1: {
                // 客户端传过来的数据
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);

                // 被查询者的uid
                String uid = nwObj.getString("uid");
                // 数据查询范围的起始时间（此参数为空表示查询时不区分时间范围），形如：“2019-01-01 10:02:02”
                String startTime = nwObj.getString("starttime");

                // 是否需要查询时间范围条件
                boolean hasTimeCondition = (!CommonUtils.isStringEmpty(startTime, true));
                // 额外的时间日期SQL过滤条件
                String timeFilter = (hasTimeCondition
                        ? " (msg_time BETWEEN '" + startTime + "' and " + DBDepend.getDefaultDatetimeFunc() + ")"
                        : "");

                StringBuilder sb = new StringBuilder();

                //## 首先构建要查询数据的SQL语句：
                //   此语句由多层子查询构成，因为要在原先简单的数据库表结构的基础上实现这种
                //   较复杂的数据查询逻辑，且需考虚查询性能的情况下，子查询是必须要使用的。

                /* 用所有“我”与对方或我参与的群聊的最近一次聊天消息记录的id，再级连查出这些聊天记录的完整内容 */
                // 提示：以下是一个非常复杂的嵌套子查询SQL语句，目的是力求一个完整SQL查出需要的数据，减少数据库查询次数
                sb.append("SELECT ")
                        .append("	  XX.fid")           // 结果字段1：聊天对象的uid

                        // 结果字段2：聊天对象的昵称
//				.append("	, ZZ.nickname" )      // v7.3前的写法
//				.append("	, (select "+DBDepend.getNVLFunc("remark", "ZZ.nickname")+" from chat_my_firends where user_uid='400071' and friend_user_uid=ZZ.user_uid) as remark " )
                        .append("	, " + DBDepend.getNVLFunc("(select remark from chat_my_firends where user_uid='" + uid + "' and friend_user_uid=ZZ.user_uid)", "ZZ.nickname") + " as remark ")

                        .append("	, YY.msg_type")      // 结果字段3：聊天消息类型，见MsgBodyRoot类中的定义
                        .append("	, YY.msg_content")   // 结果字段4：聊天消息内容
                        .append("	, YY.msg_time")      // 结果字段5：聊天时间
                        .append("	, YY.msg_time2")     // 结果字段6：聊天时间（java时间戳长整形）
                        .append("	, YY.src_uid ")       // 结果字段7：此行聊天记录的源uid（保留字段，暂未用到）
                        /* 结果字段8：判断聊天对象是否是“我”的好友（当VV.user_uid为null时即表示无匹配记录），返回值是0（否）或1（是）哦 */
//				.append("	, (case VV.user_uid when null then 0 else 1 end) as isFriends ")
                        // 注意：sql server中，遇到null值时，如果case xxxx when null这种写法，则即便 xxxx是null,得到的结果也是1，这很可能是sql server的bug！
                        .append("	, (case when VV.user_uid is null then 0 else 1 end) as isFriends ")
//				.append("	/*, case when XX.fid=YY.src_uid then 1 else 0 end as isCommingMsg*/ /* 表示是对方发给“我”的消息，否则是“我”发给“他”的 */")
                        .append("	, YY.chat_type ")      // 结果字段9：聊天模式，详见 MsgBodyRoot类常量
                        .append("	, CASE YY.chat_type WHEN 2 then dest_uid else null end as g_id ")// 结果字段10：群id（群聊消息时有意义）
                        .append("	, WW.g_name ")         // 结果字段11：群名称（群聊消息时有意义）
                        .append("FROM ")
                        .append("(")
                        /* 再将“我”的所有聊天记录按照聊天者进行分组，并对每个分组按聊天记录id进行逆序排序，并取出每个分组中id最大的记录（即我与每个人最近一次的聊天消息） */
                        .append("	SELECT X.fid AS fid,max(X.collect_id) AS collect_id FROM ")
                        .append("	(")
                        /* [单聊消息]先将所有“我”主动聊天的人，或者“我”被动聊天的人的消息，进行并集，合并成我的所有聊天记录 */
                        .append("		SELECT dest_uid AS fid,collect_id FROM chat_msgs_collect WHERE src_uid=" + uid + " AND chat_type IN(0,1) ")
                        .append("		UNION ")
                        .append("		SELECT src_uid AS fid,collect_id FROM chat_msgs_collect WHERE dest_uid=" + uid + " AND chat_type IN(0,1) ")
                        .append("		UNION")
                        /* [群聊消息]查询我所在所有群组收到的消息（本SQL的where查询条件如用IN则更简单，但EXISTS语句性能更好，所以用之） */
                        .append("		SELECT dest_uid AS fid,collect_id FROM chat_msgs_collect AAA ")
                        .append("		WHERE EXISTS (")
                        .append("			select 1 from (")
                        .append("				select A.g_id FROM (select g_id from chat_group_node where user_uid='" + uid + "') A join chat_group_base B on A.g_id=B.g_id and B.g_status=1 ")
                        .append("			)AA where AA.g_id=dest_uid ")
                        .append("		) ")
                        // @since 4.5b20190401: 额外条件实现聊天记录只查到该用户能看到的聊天时间之后（用于防止用户能看到入群前的聊天记录，以及实现首页“消息”中“删除”消息的功能）
                        .append("		and EXISTS(select 1 from chat_group_node B where B.g_id=AAA.dest_uid and B.user_uid=" + uid + " and (AAA.msg_time >= B.msg_time_start or B.msg_time_start is null)) ")
                        .append("		and chat_type=2 ")
                        .append("	) X ")
                        .append("	GROUP BY X.fid ")
//				.append("	/*ORDER BY X.collect_id desc*/")
                        .append(") XX ")
                        .append("JOIN chat_msgs_collect YY ON XX.collect_id = YY.collect_id ")
                        .append("LEFT JOIN g_users ZZ ON XX.fid = ZZ.user_uid ")
                        .append("LEFT JOIN chat_group_base WW ON (CASE YY.chat_type WHEN 2 then dest_uid else null end)=WW.g_id ")
                        .append("LEFT JOIN chat_my_firends VV ON VV.user_uid=" + uid + " AND XX.fid=VV.friend_user_uid ")
                        .append("WHERE " + timeFilter)
                        .append("ORDER BY YY.msg_time DESC ");

                Vector<Vector<?>> vecs = db.queryData(sb.toString());

                return JSON.toJSONString(vecs);

            }

            // 【接口1008-26-8】:查询聊天记录（已用于：Web端）。具体说明，详见“RainbowChat4.0-HttpRest接口手册.pdf”！
            //  支持：一对一聊天记录的返回（即“我”与指定uid用户的记录）、群聊记录的返回
            //  · 用途说明：用于从服务端查询最近一段时间的聊天记录；
            //  · 数据说明：因服务端存在超期聊天记录将被转储的功能，因而本接口的数据只包含转储超时期限内的；
            //  · 适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端（虽然移动端等也可以用，但没有
            //             必要，原因为Android或iOS端有本地缓存数据，直接本地查询即可，无需连服务端查询）。
            case ACTION_APPEND2: {
                // 客户端传过来的数据
                JSONObject nwObj = JSONObject.parseObject(newDataJSON);

                String gid = nwObj.getString("gid");   // 查群聊记录必填参数：要查询的群id


                String luid = nwObj.getString("luid"); // 查单聊或群聊记录必填参数：local uid（即“我”的uid）

                String ruid = nwObj.getString("ruid"); // 查单聊记录选填参数：remote uid（即“对方”的uid）

                // true表示当前要查询的是群聊消息记录，否则表示要查询的一对一聊天记录
                boolean isQueryGroupChatting = (!CommonUtils.isStringEmpty(gid, true));

                String orderby = nwObj.getString("orderby");     // 排序方式： 1 表示按消息时间DESC逆序，0 表示按消息时间ASC顺序排序

                String startTime = nwObj.getString("starttime"); // 聊天记录查询范围的起始时间（为空表示不区分时间范围），形如：“2019-01-01 10:02:02”
                String endTime = nwObj.getString("endtime");     // 聊天记录查询范围的结束时间（为空表示查询截止当前时间），形如：“2019-01-01 10:02:02”

                // 是否需要查询时间范围条件
                boolean hasTimeCondition = (!CommonUtils.isStringEmpty(startTime, true));

                StringBuilder sb = new StringBuilder();

                String filter = "1=1";

                // 查询过滤条件
                if (isQueryGroupChatting) {
                    String[] arr = gid.split("_");
                    String channelId = arr[1];
                    String groupId = arr[0];
                    // 群聊查询条件
                    filter = "(chat_type=2 and dest_uid=" + gid + " and " +
                            // @since 4.5b20190401: 额外条件实现聊天记录只查到该用户能看到的聊天时间之后（用于防止用户能看到入群前的聊天记录，以及实现首页“消息”中“删除”消息的功能）
                            "	EXISTS(select 1 from chat_group_node b where b.base_id ='" + groupId + "' and node_id = '" + channelId + "' and b.user_uid='" + luid + "' and (a.msg_time >= b.msg_time_start or b.msg_time_start is null))" +
                            ")";
                } else {
                    // 单聊查询条件
                    filter = "((src_uid='" + luid + "' and dest_uid='" + ruid
                            + "') or (src_uid='" + ruid + "' and dest_uid='" + luid + "'))";
                }

                // 额外的时间日期过滤条件
                String timeFilter = (hasTimeCondition
                        ? "and (msg_time BETWEEN '" + startTime + "' and " + (CommonUtils.isStringEmpty(endTime, true) ? DBDepend.getDefaultDatetimeFunc() : "'" + endTime + "'") + ")"
                        : "");

                // 组合成完整的sql语句
                sb.append("select ")
                        .append("	src_uid, dest_uid, chat_type, msg_type, msg_content")
                        // 将msg_time2字段的无时区时间戳传给客户端，以便客户端按其所有时区显示为正确的时间
                        .append("	,msg_time2 ")
//					.append("	,DATE_FORMAT(msg_time,'"+DATETIME_FORMAT_FROM_MYSQL_YYYY_MM_DD_HHMMSS+"') as msg_time")
                        .append("	,fingerprint ")
                        .append("	,parent_fp ")
                        .append("from chat_msgs_collect a ")
                        .append("where " + filter + " " + timeFilter)
                        .append("order by collect_id " + ("1".equals(orderby) ? "desc" : "asc"));// 使用collect_id排序比msg_time更精确（因为理论上存在时间重复的可能性）

                Vector<Vector<?>> vecs = db.queryData(sb.toString());

                return JSON.toJSONString(vecs);
            }

            default:
                throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
        }
    }

    /**
     * 准备好删除单聊（一对一好友、一对一陌生人）的聊天记录和离线消息的SQL。
     * <p>
     * 说明：单聊消息不是真的从物理上删除，只是逻辑上实现“删除”而已（即转储到存档表里而已）。
     *
     * @param myUid     聊天记录中的“我”
     * @param friendUid 聊天记录中的“好友”
     * @since 4.5
     */
    private static void prepareDeleteSingleChattingMsg(Vector<String> sqls
            , Vector<Object[]> values, String myUid, String friendUid) throws Exception {
        //********************* 先尝试存档聊天记录 ************************//
        // 好友记录的SQL条件
        String conditionSQL = "((src_uid=" + myUid + " and dest_uid=" + friendUid + ")" +
                " or (src_uid=" + friendUid + " and dest_uid=" + myUid + "))";
        if (!LogicProcessor2.db.queryData("select 1 from chat_msgs_collect where " + conditionSQL).isEmpty()) {
            // 先将聊天记录先插入到存档表中
            sqls.add(TimmingRestoreMessages.COPY_CHATTING_MESSAGES_TO_ACHIVED_SQL + " where " + conditionSQL);
            values.add(new Object[]{});

            // 再将聊天记录从聊天记录表中移除
            sqls.add(TimmingRestoreMessages.DELETE_CHATTING_MESSAGES_SQL + " where " + conditionSQL);
            values.add(new Object[]{});
        }

        //********************* 再尝试删除未拉取的离线消息 ************************//
        // 离线消息的SQL条件
        String conditionSQL2 = "((user_uid=" + myUid + " and friend_user_uid=" + friendUid + ")" +
                " or (user_uid=" + friendUid + " and friend_user_uid=" + myUid + "))";
        if (!db.queryData("select 1 from chat_offline_history where " + conditionSQL2).isEmpty()) {
            // 直接将超出期限的离线消息从离线消息表中移除
            sqls.add("DELETE FROM chat_offline_history where " + conditionSQL2);
            values.add(new Object[]{});
        }
    }

    /**
     * 从查询出来的数据库行中解析并构建离线好友请求数据对象（供客户端使用）。
     */
    private static RosterElementEntity constructOfflineAddFriendReqObj(Vector<?> row) throws Exception {
        if (row != null) {
            String reqSrcUid = (String) row.get(0);
            String reqTime = (String) row.get(1);
            String saySomethingFromHim = (String) row.get(2);

            RosterElementEntity ree = LogicUtils.queryUserInfoByUID(reqSrcUid, null);
            if (ree != null) {
                // 自20140213 RainbowChat2.2（含）以后ex1作为服务端转发加好友请求（给B）时存放“验证说明文本”使用（包括离线消息）
                ree.setEx1(MyControllerJSONP.JSONSave(saySomethingFromHim));
                // 自20171225 RainbowChat4.2（含）以后ex10作为服务端转发加好友请求（给B）时存放“好友请求发起时间戳”使用（包括离线消息）
                // 自20180507 RBv4.3以后，本字段存放的是时间戳，而非人类可读的时间字串
                ree.setEx10(reqTime);
            }

            return ree;
        }

        return null;
    }
}
